library(tidyverse)
library(modelr)
library(plotly)
mpg <- ggplot2::mpg
mpg_manual <- mpg %>% 
  filter(trans %in% c('manual(m5)', 'manual(m6)'))
library(openintro)
data(mariokart)
mario_kart <- mariokart %>% 
  filter(total_pr < 100)
rm(mariokart)
colnames(mario_kart) <- c("ID", "duration", "nBids", "cond", "startPr", "shipPr", "totalPr", "shipSp", "sellerRate", "stockPhoto", "wheels", "title")
SAT <- read.csv('/home/cla/Documentos/Vitor/DataCamp/Statistician-with-R//Datasets/SAT.csv')

Adding a numerical explanatory variable

1. Adding a numerical explanatory variable

Thus far, we’ve only considered multiple regression models with one numeric explanatory variable. In this chapter, we will explore models that have at least two numeric explanatory variables.

2. Adding a second numeric explanatory variable

Mathematically, adding a second numeric explanatory variable is trivial—we just add another term to our model. In this example, we are modeling the birthweight of babies born in San Francisco as a function of their pregnancy’s length of gestation (in weeks) and the mother’s age (in years). Note that both gestation and age are numeric variables here, so this is not a parallel slopes model. The syntax for fitting this model in R is similarly trivial—we simply extend the formula to incorporate both the gestation and age variables, just as we did before. Note also that since age is not categorical, we don’t have to worry about using the factor() function, or converting a categorical variable into binary variables, like we had to with year and is_newer.

lm(bwt ~ gestation + age, data = babies)

Call:
lm(formula = bwt ~ gestation + age, data = babies)

Coefficients:
(Intercept)    gestation          age  
   -15.5226       0.4676       0.1657  

3. No longer a 2D problem

Unfortunately, while the mathematical and syntactical formulations of multiple regression models are easy extensions of things we already know, a visual formulation of these models becomes much trickier. You might be tempted to visualize our model using a ggplot expression like this. But unfortunately, this will not work, because ggplot only handles 2D graphics, and thus there is no z aesthetic.

ggplot(data = babies, aes(x = gestation, y = age, z = bwt)) +
  geom_point() +
  geom_smooth(method = 'lm', se = FALSE)

4. Data space is 3D

Our data space is now three dimensional, because bwt, gestation, and age are all numeric variables that our model encapsulates. So we will need to get a little bit creative in order to create a meaningful visualization of our model.

data_space1 <- ggplot(data = babies, aes(x = gestation, y = age)) +
  geom_point(aes(color = bwt)) 
data_space1

5. Tiling the plane

One way to visualize a 3D model is to tile the plane. That is, we will create a 2D plot that covers all combinations of our two explanatory variables, and we will use color to reflect the corresponding fitted values. Here, we have created that grid of values using the data_grid() function, and then fed those into our model using the augment() function with the newdata argument. These two steps create a data frame of all possible combinations of gestation and age values, along with the model prediction for each.

library(broom)

grid1 <- babies %>% 
  data_grid(
    gestation = seq_range(gestation, by = 1),
    age = seq_range(age, by = 1)
  )
mod1 <- lm(bwt ~ gestation + age, data = babies)

bwt_hats <- augment(mod1, newdata = grid1)

6. Tiles in the data space

We then use the geom_tile() function to superimpose these values on our data space. Setting the alpha argument to 0 point 5 allows us to see both the actual observations (as points) and the model predictions (as a smooth surface). Note how the color of the tiles lighten as you get closer to the upper right corner. This reflects that the model predicts heavier babies for older mothers with longer gestational periods.

mode_space1 <- data_space1 +
  geom_tile(data = bwt_hats, aes(fill = .fitted, alpha = 0.5)) +
  scale_fill_continuous('bwt', limits = range(babies$bwt))

7. 3D visualization

A perhaps more natural way to visualize a multiple regression model is as a plane through a cloud of points in three dimensions. Here, we use the plotly package to illustrate our model for the birthweight of babies. Note how the plane cuts through the data points. The residual associated with each observation is the vertical distance between that point and the plane. Believe it or not, this plane is the one that uniquely minimizes the sum of of the squared residuals between itself and these data points. The plot_ly syntax is similar to that of ggplot. In this case, the add_markers() function draws the points, while the add_surface() function draws the plane. The x, y, and plane objects were created in code that we aren’t showing here due to its complexity.

plot_ly(data = babies, z = ~bwt, x = ~gestation, y = ~age, opacity = 0.6) %>% 
  add_markers(marker = list(size = 2)) %>% 
  add_surface(x = ~x, y = ~y, z = ~plane, showscale = FALSE, cmin = 0, cmax = 1, surfacecolor = 'grey', colorscale = 'grey')
Ignoring 15 observationsminimal value for n is 3, returning requested palette with 3 different levels
Ignoring 15 observationsminimal value for n is 3, returning requested palette with 3 different levels

8. Let’s practice!

Now it’s time for you to build some multiple regression models.

Fitting a MLR model

In terms of the R code, fitting a multiple linear regression model is easy: simply add variables to the model formula you specify in the lm() command.

In a parallel slopes model, we had two explanatory variables: one was numeric and one was categorical. Here, we will allow both explanatory variables to be numeric.

Instructions

The dataset mario_kart is already loaded in your workspace.

  • Fit a multiple linear regression model for total price as a function of the duration of the auction and the starting price.
# Fit the model using duration and startPr
lm(totalPr ~ duration + startPr, data = mario_kart)

Call:
lm(formula = totalPr ~ duration + startPr, data = mario_kart)

Coefficients:
(Intercept)     duration      startPr  
     51.030       -1.508        0.233  

Tiling the plane

One method for visualizing a multiple linear regression model is to create a heatmap of the fitted values in the plane defined by the two explanatory variables. This heatmap will illustrate how the model output changes over different combinations of the explanatory variables.

This is a multistep process:

  • First, create a grid of the possible pairs of values of the explanatory variables. The grid should be over the actual range of the data present in each variable. We’ve done this for you and stored the result as a data frame called grid.
  • Use augment() with the newdata argument to find the \(\h\t{y\)}’s corresponding to the values in grid.
  • Add these to the data_space plot by using the fill aesthetic and geom_tile().

Instructions

The model object mod is already in your workspace.

  • Use augment() to create a data.frame that contains the values the model outputs for each row of grid.
  • Use geom_tile to illustrate these predicted values over the data_space plot. Use the fill aesthetic and set alpha = 0.5.
# add predictions to grid
price_hats <- augment(mod, newdata = grid)

# tile the plane
data_space + 
  geom_tile(data = price_hats, aes(fill = .fitted), alpha = 0.5) 

Models in 3D

An alternative way to visualize a multiple regression model with two numeric explanatory variables is as a plane in three dimensions. This is possible in R using the plotly package.

We have created three objects that you will need:

  • x: a vector of unique values of duration
  • y: a vector of unique values of startPr
  • plane: a matrix of the fitted values across all combinations of x and y
x <- seq_range(mario_kart$duration, n = 70)
y <- seq_range(mario_kart$startPr, n = 70)
new_data <- data.frame(duration = x, startPr = y)
plane <- matrix(nrow = 70, ncol = 70)
for (i in seq_along(x)) {
  plane[, i] <- predict(mod, data.frame(duration = x, startPr = y[i]))
}  

Much like ggplot(), the plot_ly() function will allow you to create a plot object with variables mapped to x, y, and z aesthetics. The add_markers() function is similar to geom_point() in that it allows you to add points to your 3D plot.

Note that plot_ly uses the pipe (%>%) operator to chain commands together.

Instructions

  • Run the plot_ly command to draw 3D scatterplot for totalPr as a function of duration and startPr by mapping the z variable to the response and the x and y variables to the explanatory variables. Duration should be on the x-axis and starting price should be on the y-axis.
  • Use add_surface() to draw a plane through the cloud of points by setting z = ~plane.
# draw the 3D scatterplot
p <- plot_ly(data = mario_kart, z = ~totalPr, x = ~duration, y = ~startPr, opacity = 0.6) %>%
  add_markers(marker = list(size = 2)) 
  
# draw the plane
p %>%
  add_surface(x = ~x, y = ~y, z = ~plane, showscale = FALSE)

Conditional interpretation of coefficients

1. Conditional interpretation of coefficients

What do the coefficients in a multiple regression model mean?

2. Two slope coefficients

Consider the fitted coefficients for our model for the birthweight of babies shown above. Since both of our explanatory variables are numeric, the coefficients of both gestation and age represent slopes. But slopes of what? A line certainly can’t have two slopes, but as we saw previously, our model is not a line: it is a plane. And a plane can have two slopes. Note that in this case both slopes are positive, but have different magnitudes.

mod1

Call:
lm(formula = bwt ~ gestation + age, data = babies)

Coefficients:
(Intercept)    gestation          age  
   -15.5226       0.4676       0.1657  

3. Tiled plane

When we depicted our model as a tiled surface in the plane, we noticed that the color of the tiles got lighter and bluer as we moved towards the upper right hand corner of the plot. This reflects the fact that our model predicts higher birthweights for babies with longer gestational periods born to older mothers. But at what rate does this color change?

mode_space1

4. Tiled plane plus first slope

For a 30-year-old mother, the increase in expected birthweight is 0 point 47 ounces per day, and this can be viewed on the plot as the rate of color change as you move horizontally through the plot along a straight line. It is in this sense that the coefficient of gestation is a slope. However, this rate of change is constant across mothers of all ages. It doesn’t matter whether you are 20, 30, or 40. While the expected birthweight does depend on the mother’s age, the rate of change in birthweight with respect to gestational length does not depend on the the mother’s age. Thus, the coefficient of 0.47 ounces per day on gestation reflects the slope of the plane for a fixed value of age. And since this slope doesn’t change with respect to age, we often say that it reflects the effect of gestational length upon birthweight, while holding age constant.

mode_space1 + geom_hline(yintercept = 30, color = 'red')

5. Tiled plane plus second slope

Similarly, at the typical gestational length of 40 weeks (or 280 days), the expected birthweight increases at a rate of 0 point 17 ounces per year of the mother’s age. That is, our model predicts that the birthweight of a 36-year-old mother is about one-sixth of an ounce heavier than the birthweight of a 35-year-old mother, when both mothers carry full term. But again, that rate of change is the same irrespective of the gestational length, so we would expect the same difference for babies born at 35 weeks, 40 weeks, and 42 weeks. So the coefficient of 0 point 17 ounces per year is the slope of the plane for a fixed length of gestation. Again, since this slope is the same for all gestational lengths, we can interpret this coefficient as the effect of the mother’s age on birthweight, while holding gestational length constant. It’s also apparent from the plot that the colors change more rapidly as you move horizontally as opposed to vertically. This reflects the fact that the coefficient on gestation is bigger than the coefficient on age.

mode_space1 + geom_vline(xintercept = 280, color = 'red')

6. Coefficient interpretation

Now, you might be tempted to think that bigger coefficients are always more important, but this is not true. The value of the coefficients depend on the units of the explanatory variables. In this case, one variable is in the units of days (of gestational length), while the other is in the units of years (of mother’s age). They are not directly comparable. When you interpret coefficients from a multiple regression model, be sure to always include a phrase to the effect of “holding x constant.” Another good alternative is “after controlling for x.” This information is crucial to having a valid understanding of a regression model.

7. Let’s practice!

You’ll demonstate your understanding of these concepts in the next exercises.

Adding a third (categorical) variable

1. Adding a third (categorical) variable

2. How could we forget about smoking?

No study of birthweight is complete without some discussion of the effect of smoking, which is known to have all kinds of undesireable health consequences. Mothers in the babies data set have their smoking status recorded as a binary variable: either she was or was not a smoker. Adding this third explanatory variable to our model is again easy. Since this variable is encoded as 0 or 1, no transformation is necessary and we can simply add another term to the mathematical model and the formula syntax to specify our new model.

3. Geometry

But here again the geometric changes are not as easy to see. However, our emphasis on the geometry of these models should give you some intuition. Recall that the addition of a categorical explanatory variable to a numeric explanatory variable changed a line into parallel lines. Moreover, a model with two numeric explanatory variables was a plane. Can you guess how the addition of a categorical explanatory variable will change the geometry of a model with two numeric explanatory variables? If you guess parallel planes, you’re right!

4. Drawing parallel planes in 3D

Once again, we can use plotly to create a 3D image of our model in the data space. Here we have used the add_surface() function twice: once to add a plane for non-smokers, and again to add another plane for smokers. The plane on “top” is the one for non-smokers—can you think of why this might be case? We’ll return to this question in a minute.

plot_ly(data = babies, z = ~bwt, x = ~gestation, y = ~age, opacity = 0.6) %>% 
  add_markers(color = ~factor(smoke), marker = list(size = 2)) %>% 
  add_surface(x = ~x, y = ~y, z = ~plane0, showscale = FALSE, cmin = 0, cmax = 1, surfacecolor = 'red', colorscale = 'red') %>% 
  add_surface(x = ~x, y = ~y, z = ~plane1, showscale = FALSE, cmin = 0, cmax = 1, surfacecolor = 'blue', colorscale = 'blue')
Ignoring 15 observationsSome values were outside the color scale and will be treated as NAminimal value for n is 3, returning requested palette with 3 different levels
Ignoring 15 observationsSome values were outside the color scale and will be treated as NAminimal value for n is 3, returning requested palette with 3 different levels

5. Coefficient interpretation

The interpretations that we developed previously still hold—we just need to think carefully about how they apply to our new model. The coefficients on gestation and age still reflect slopes. And since the planes are parallel, both slopes are the same in both planes. The coefficient on smoke is the distance between the two planes, and this distance is constant across all possible values of gestation and age. In this sense, we are modeling the effect of smoking as being the same regardless of gestational length and the mother’s age. However, we have estimated the size of that effect in the context of gestational length and the mother’s age. The coefficient of gestation is 0.45 ounces per day, which is only slightly less than it was in our previous model that did not consider smoking. Our interpretation is that each additional day of gestation was associated with an increase in expected birthweight of 0.45 ounces, after controlling for the mother’s age and smoking status. Each additional year of the mother’s age was associated with an increase in expected birthweight of just 0.11 ounces per year, after controlling for gestational length and the mother’s smoking status. Note that the effect of the mother’s age on birthweight appears lower than in the previous model. Let’s focus now on the effect of smoking. The coefficient on smoke is -8.01 ounces. Thus, our model predicts that mothers who smoke will deliver babies that weight 8 ounces less, on average, than mothers of the same age and gestational length who don’t smoke. That is, the negative effect of smoking on expected birthweight is 8 ounces (or half a pound), after controlling for gestational length and the mother’s age. This is a huge effect compared to the influence of the other two variables we have considered.

mod1; mod2
Erro: objeto 'mod1' não encontrado

6. Let’s practice!

Now it’s your turn to build and interpret some parallel planes models.

Visualizing parallel planes

By including the duration, starting price, and condition variables in our model, we now have two explanatory variables and one categorical variable. Our model now takes the geometric form of two parallel planes!

The first plane corresponds to the model output when the condition of the item is new, while the second plane corresponds to the model output when the condition of the item is used. The planes have the same slopes along both the duration and starting price axes—it is the \(z\)-intercept that is different.

Once again we have stored the x and y vectors for you. Since we now have two planes, there are matrix objects plane0 and plane1 stored for you as well.

mod <- lm(totalPr ~ duration + startPr + factor(cond), data = mario_kart)
x <- seq_range(mario_kart$duration, n = 70)
y <- seq_range(mario_kart$startPr, n = 70)
w0 <- rep('new', 70)
w1 <- rep('used', 70)
new_data0 <- data.frame(duration = x, startPr = y, cond = w0)
new_data1 <- data.frame(duration = x, startPr = y, cond = w1)
plane0 <- matrix(nrow = 70, ncol = 70)
for (i in seq_along(x)) {
  plane0[, i] <- predict(mod, data.frame(duration = x, startPr = y[i], cond = factor(w0)))
}  
plane1 <- matrix(nrow = 70, ncol = 70)
for (i in seq_along(x)) {
  plane1[, i] <- predict(mod, data.frame(duration = x, startPr = y[i], cond = w1))
}

Instructions

  • Use plot_ly to draw 3D scatterplot for totalPr as a function of duration, startPr, and cond by mapping the z variable to the response and the x and y variables to the explanatory variables. Duration should be on the x-axis and starting price should be on the y-axis. Use color to represent cond.
  • Use add_surface() (twice) to draw two planes through the cloud of points, one for new MarioKarts and another for used ones. Use the objects plane0 and plane1.
# draw the 3D scatterplot
p <- plot_ly(data = mario_kart, z = ~totalPr, x = ~duration, y = ~startPr, opacity = 0.6) %>%
  add_markers(color = ~cond) 
  
# draw two planes
p %>%
  add_surface(x = ~x, y = ~y, z = ~plane0, showscale = FALSE) %>%
  add_surface(x = ~x, y = ~y, z = ~plane1, showscale = FALSE)
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels

Higher dimensions

1. Higher dimensions

2. Adding more variables

By now, you may have caught on to the fact that there are no mathematical or syntactical hurdles to adding more variables to a multiple regression model. You can add as many terms as you want and R will happily fit the model for you. In this case we added explanatory variables for the mother’s height and weight, and a variable called parity that denotes whether this child was her first pregnancy. It seems reasonable to think that any of these variables might play a role in determining a child’s birthweight, and so there may be valid scientific reasons for including any or all of these variables in our model. In R, you can use the dot operator in a formula to mean “all other variables in the data.frame.” In the last expression above, we have built the “kitchen sink” model by throwing everything in with the dot operator—with one exception. There are often variables that are recorded that will not make sense to include in a model. The case variable in the babies data set simply identifies each case—it is not an observed property of each birth. Therefore, we exclude it explicity. You can read the dot - case formula as “all variables except for the one named case.”

lm(bwt ~ gestation + age + smoke + height + weight + parity, data = babies)

Call:
lm(formula = bwt ~ gestation + age + smoke + height + weight + 
    parity, data = babies)

Coefficients:
(Intercept)    gestation          age        smoke  
  -80.41085      0.44398     -0.00895     -8.40073  
     height       weight       parity  
    1.15402      0.05017     -3.32720  
# same model (but note order of coefficients)
lm(bwt ~. - case, data = babies)

Call:
lm(formula = bwt ~ . - case, data = babies)

Coefficients:
(Intercept)    gestation       parity          age  
  -80.41085      0.44398     -3.32720     -0.00895  
     height       weight        smoke  
    1.15402      0.05017     -8.40073  

3. Higher dimensional geometry

You may also have caught on to the fact that a geometric interpretation of these higher order models is extremely difficult. There are things called hyperplanes which generalize the notion of a plane to higher dimensions, but since we humans can’t visualize more than three spatial dimensions anyway, there isn’t much hope of visualizing our higher dimensional models in their full glory. We can of course employ tricks like mapping variables to color, creating small multiples for categorical variables, and projecting into a lower-dimensional space by hard-coding the values of some variables. We won’t explore that further here since these plots are unlikely to reinforce your geometric intuition.

4. Interpretation in large models

The interpretation of the coefficients in larger models remains the same. We can still think of the coefficients on numeric explanatory variables as being slopes, and we can still think of the coefficients on categorical explanatory variables as being intercepts. The main thing we have to be careful about is remembering to include language specifying the other variables in the model. Here, our main finding would likely be that the expected birthweight of babies born to mothers who smoke is 8.4 ounces lower than mothers who don’t, after controlling for gestational length, and her age, height, weight, and whether she had previous pregnancies.

lm(bwt ~ gestation + age + smoke + height + weight + parity, data = babies)

5. Let’s practice!

Let’s see if you can find a correct interpretation for coefficients in a large model.

LS0tCnRpdGxlOiAiMDMgLSBNdWx0aXBsZSBSZWdyZXNzaW9uIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogZmFsc2UKICAgIHRvY19kZXB0aDogNAogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShtb2RlbHIpCmxpYnJhcnkocGxvdGx5KQptcGcgPC0gZ2dwbG90Mjo6bXBnCm1wZ19tYW51YWwgPC0gbXBnICU+JSAKICBmaWx0ZXIodHJhbnMgJWluJSBjKCdtYW51YWwobTUpJywgJ21hbnVhbChtNiknKSkKbGlicmFyeShvcGVuaW50cm8pCmRhdGEobWFyaW9rYXJ0KQptYXJpb19rYXJ0IDwtIG1hcmlva2FydCAlPiUgCiAgZmlsdGVyKHRvdGFsX3ByIDwgMTAwKQpybShtYXJpb2thcnQpCmNvbG5hbWVzKG1hcmlvX2thcnQpIDwtIGMoIklEIiwgImR1cmF0aW9uIiwgIm5CaWRzIiwgImNvbmQiLCAic3RhcnRQciIsICJzaGlwUHIiLCAidG90YWxQciIsICJzaGlwU3AiLCAic2VsbGVyUmF0ZSIsICJzdG9ja1Bob3RvIiwgIndoZWVscyIsICJ0aXRsZSIpClNBVCA8LSByZWFkLmNzdignL2hvbWUvY2xhL0RvY3VtZW50b3MvVml0b3IvRGF0YUNhbXAvU3RhdGlzdGljaWFuLXdpdGgtUi8vRGF0YXNldHMvU0FULmNzdicpCmBgYAoKIyMgKipBZGRpbmcgYSBudW1lcmljYWwgZXhwbGFuYXRvcnkgdmFyaWFibGUqKgoKKioxLiBBZGRpbmcgYSBudW1lcmljYWwgZXhwbGFuYXRvcnkgdmFyaWFibGUqKgoKVGh1cyBmYXIsIHdlJ3ZlIG9ubHkgY29uc2lkZXJlZCBtdWx0aXBsZSByZWdyZXNzaW9uIG1vZGVscyB3aXRoIG9uZSBudW1lcmljIGV4cGxhbmF0b3J5IHZhcmlhYmxlLiBJbiB0aGlzIGNoYXB0ZXIsIHdlIHdpbGwgZXhwbG9yZSBtb2RlbHMgdGhhdCBoYXZlIGF0IGxlYXN0IHR3byBudW1lcmljIGV4cGxhbmF0b3J5IHZhcmlhYmxlcy4KCioqMi4gQWRkaW5nIGEgc2Vjb25kIG51bWVyaWMgZXhwbGFuYXRvcnkgdmFyaWFibGUqKgoKTWF0aGVtYXRpY2FsbHksIGFkZGluZyBhIHNlY29uZCBudW1lcmljIGV4cGxhbmF0b3J5IHZhcmlhYmxlIGlzIHRyaXZpYWwtLS13ZSBqdXN0IGFkZCBhbm90aGVyIHRlcm0gdG8gb3VyIG1vZGVsLiBJbiB0aGlzIGV4YW1wbGUsIHdlIGFyZSBtb2RlbGluZyB0aGUgYmlydGh3ZWlnaHQgb2YgYmFiaWVzIGJvcm4gaW4gU2FuIEZyYW5jaXNjbyBhcyBhIGZ1bmN0aW9uIG9mIHRoZWlyIHByZWduYW5jeSdzIGxlbmd0aCBvZiBnZXN0YXRpb24gKGluIHdlZWtzKSBhbmQgdGhlIG1vdGhlcidzIGFnZSAoaW4geWVhcnMpLiBOb3RlIHRoYXQgYm90aCBnZXN0YXRpb24gYW5kIGFnZSBhcmUgbnVtZXJpYyB2YXJpYWJsZXMgaGVyZSwgc28gdGhpcyBpcyBub3QgYSBwYXJhbGxlbCBzbG9wZXMgbW9kZWwuIFRoZSBzeW50YXggZm9yIGZpdHRpbmcgdGhpcyBtb2RlbCBpbiBSIGlzIHNpbWlsYXJseSB0cml2aWFsLS0td2Ugc2ltcGx5IGV4dGVuZCB0aGUgZm9ybXVsYSB0byBpbmNvcnBvcmF0ZSBib3RoIHRoZSBnZXN0YXRpb24gYW5kIGFnZSB2YXJpYWJsZXMsIGp1c3QgYXMgd2UgZGlkIGJlZm9yZS4gTm90ZSBhbHNvIHRoYXQgc2luY2UgYWdlIGlzIG5vdCBjYXRlZ29yaWNhbCwgd2UgZG9uJ3QgaGF2ZSB0byB3b3JyeSBhYm91dCB1c2luZyB0aGUgZmFjdG9yKCkgZnVuY3Rpb24sIG9yIGNvbnZlcnRpbmcgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBpbnRvIGJpbmFyeSB2YXJpYWJsZXMsIGxpa2Ugd2UgaGFkIHRvIHdpdGggeWVhciBhbmQgaXNfbmV3ZXIuCgpgYGB7cn0KbG0oYnd0IH4gZ2VzdGF0aW9uICsgYWdlLCBkYXRhID0gYmFiaWVzKQpgYGAKCioqMy4gTm8gbG9uZ2VyIGEgMkQgcHJvYmxlbSoqCgpVbmZvcnR1bmF0ZWx5LCB3aGlsZSB0aGUgbWF0aGVtYXRpY2FsIGFuZCBzeW50YWN0aWNhbCBmb3JtdWxhdGlvbnMgb2YgbXVsdGlwbGUgcmVncmVzc2lvbiBtb2RlbHMgYXJlIGVhc3kgZXh0ZW5zaW9ucyBvZiB0aGluZ3Mgd2UgYWxyZWFkeSBrbm93LCBhIHZpc3VhbCBmb3JtdWxhdGlvbiBvZiB0aGVzZSBtb2RlbHMgYmVjb21lcyBtdWNoIHRyaWNraWVyLiBZb3UgbWlnaHQgYmUgdGVtcHRlZCB0byB2aXN1YWxpemUgb3VyIG1vZGVsIHVzaW5nIGEgZ2dwbG90IGV4cHJlc3Npb24gbGlrZSB0aGlzLiBCdXQgdW5mb3J0dW5hdGVseSwgdGhpcyB3aWxsIG5vdCB3b3JrLCBiZWNhdXNlIGdncGxvdCBvbmx5IGhhbmRsZXMgMkQgZ3JhcGhpY3MsIGFuZCB0aHVzIHRoZXJlIGlzIG5vIHogYWVzdGhldGljLgoKYGBge3J9CmdncGxvdChkYXRhID0gYmFiaWVzLCBhZXMoeCA9IGdlc3RhdGlvbiwgeSA9IGFnZSwgeiA9IGJ3dCkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScsIHNlID0gRkFMU0UpCmBgYAoKKio0LiBEYXRhIHNwYWNlIGlzIDNEKioKCk91ciBkYXRhIHNwYWNlIGlzIG5vdyB0aHJlZSBkaW1lbnNpb25hbCwgYmVjYXVzZSBid3QsIGdlc3RhdGlvbiwgYW5kIGFnZSBhcmUgYWxsIG51bWVyaWMgdmFyaWFibGVzIHRoYXQgb3VyIG1vZGVsIGVuY2Fwc3VsYXRlcy4gU28gd2Ugd2lsbCBuZWVkIHRvIGdldCBhIGxpdHRsZSBiaXQgY3JlYXRpdmUgaW4gb3JkZXIgdG8gY3JlYXRlIGEgbWVhbmluZ2Z1bCB2aXN1YWxpemF0aW9uIG9mIG91ciBtb2RlbC4KCgpgYGB7cn0KZGF0YV9zcGFjZTEgPC0gZ2dwbG90KGRhdGEgPSBiYWJpZXMsIGFlcyh4ID0gZ2VzdGF0aW9uLCB5ID0gYWdlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gYnd0KSkgCmRhdGFfc3BhY2UxCmBgYAoKKio1LiBUaWxpbmcgdGhlIHBsYW5lKioKCk9uZSB3YXkgdG8gdmlzdWFsaXplIGEgM0QgbW9kZWwgaXMgdG8gdGlsZSB0aGUgcGxhbmUuIFRoYXQgaXMsIHdlIHdpbGwgY3JlYXRlIGEgMkQgcGxvdCB0aGF0IGNvdmVycyBhbGwgY29tYmluYXRpb25zIG9mIG91ciB0d28gZXhwbGFuYXRvcnkgdmFyaWFibGVzLCBhbmQgd2Ugd2lsbCB1c2UgY29sb3IgdG8gcmVmbGVjdCB0aGUgY29ycmVzcG9uZGluZyBmaXR0ZWQgdmFsdWVzLiBIZXJlLCB3ZSBoYXZlIGNyZWF0ZWQgdGhhdCBncmlkIG9mIHZhbHVlcyB1c2luZyB0aGUgZGF0YV9ncmlkKCkgZnVuY3Rpb24sIGFuZCB0aGVuIGZlZCB0aG9zZSBpbnRvIG91ciBtb2RlbCB1c2luZyB0aGUgYXVnbWVudCgpIGZ1bmN0aW9uIHdpdGggdGhlIG5ld2RhdGEgYXJndW1lbnQuIFRoZXNlIHR3byBzdGVwcyBjcmVhdGUgYSBkYXRhIGZyYW1lIG9mIGFsbCBwb3NzaWJsZSBjb21iaW5hdGlvbnMgb2YgZ2VzdGF0aW9uIGFuZCBhZ2UgdmFsdWVzLCBhbG9uZyB3aXRoIHRoZSBtb2RlbCBwcmVkaWN0aW9uIGZvciBlYWNoLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KGJyb29tKQoKZ3JpZDEgPC0gYmFiaWVzICU+JSAKICBkYXRhX2dyaWQoCiAgICBnZXN0YXRpb24gPSBzZXFfcmFuZ2UoZ2VzdGF0aW9uLCBieSA9IDEpLAogICAgYWdlID0gc2VxX3JhbmdlKGFnZSwgYnkgPSAxKQogICkKbW9kMSA8LSBsbShid3QgfiBnZXN0YXRpb24gKyBhZ2UsIGRhdGEgPSBiYWJpZXMpCgpid3RfaGF0cyA8LSBhdWdtZW50KG1vZDEsIG5ld2RhdGEgPSBncmlkMSkKYGBgCgoqKjYuIFRpbGVzIGluIHRoZSBkYXRhIHNwYWNlKioKCldlIHRoZW4gdXNlIHRoZSBnZW9tX3RpbGUoKSBmdW5jdGlvbiB0byBzdXBlcmltcG9zZSB0aGVzZSB2YWx1ZXMgb24gb3VyIGRhdGEgc3BhY2UuIFNldHRpbmcgdGhlIGFscGhhIGFyZ3VtZW50IHRvIDAgcG9pbnQgNSBhbGxvd3MgdXMgdG8gc2VlIGJvdGggdGhlIGFjdHVhbCBvYnNlcnZhdGlvbnMgKGFzIHBvaW50cykgYW5kIHRoZSBtb2RlbCBwcmVkaWN0aW9ucyAoYXMgYSBzbW9vdGggc3VyZmFjZSkuIE5vdGUgaG93IHRoZSBjb2xvciBvZiB0aGUgdGlsZXMgbGlnaHRlbiBhcyB5b3UgZ2V0IGNsb3NlciB0byB0aGUgdXBwZXIgcmlnaHQgY29ybmVyLiBUaGlzIHJlZmxlY3RzIHRoYXQgdGhlIG1vZGVsIHByZWRpY3RzIGhlYXZpZXIgYmFiaWVzIGZvciBvbGRlciBtb3RoZXJzIHdpdGggbG9uZ2VyIGdlc3RhdGlvbmFsIHBlcmlvZHMuCgpgYGB7cn0KbW9kZV9zcGFjZTEgPC0gZGF0YV9zcGFjZTEgKwogIGdlb21fdGlsZShkYXRhID0gYnd0X2hhdHMsIGFlcyhmaWxsID0gLmZpdHRlZCwgYWxwaGEgPSAwLjUpKSArCiAgc2NhbGVfZmlsbF9jb250aW51b3VzKCdid3QnLCBsaW1pdHMgPSByYW5nZShiYWJpZXMkYnd0KSkKYGBgCgoqKjcuIDNEIHZpc3VhbGl6YXRpb24qKgoKQSBwZXJoYXBzIG1vcmUgbmF0dXJhbCB3YXkgdG8gdmlzdWFsaXplIGEgbXVsdGlwbGUgcmVncmVzc2lvbiBtb2RlbCBpcyBhcyBhIHBsYW5lIHRocm91Z2ggYSBjbG91ZCBvZiBwb2ludHMgaW4gdGhyZWUgZGltZW5zaW9ucy4gSGVyZSwgd2UgdXNlIHRoZSBwbG90bHkgcGFja2FnZSB0byBpbGx1c3RyYXRlIG91ciBtb2RlbCBmb3IgdGhlIGJpcnRod2VpZ2h0IG9mIGJhYmllcy4gTm90ZSBob3cgdGhlIHBsYW5lIGN1dHMgdGhyb3VnaCB0aGUgZGF0YSBwb2ludHMuIFRoZSByZXNpZHVhbCBhc3NvY2lhdGVkIHdpdGggZWFjaCBvYnNlcnZhdGlvbiBpcyB0aGUgdmVydGljYWwgZGlzdGFuY2UgYmV0d2VlbiB0aGF0IHBvaW50IGFuZCB0aGUgcGxhbmUuIEJlbGlldmUgaXQgb3Igbm90LCB0aGlzIHBsYW5lIGlzIHRoZSBvbmUgdGhhdCB1bmlxdWVseSBtaW5pbWl6ZXMgdGhlIHN1bSBvZiBvZiB0aGUgc3F1YXJlZCByZXNpZHVhbHMgYmV0d2VlbiBpdHNlbGYgYW5kIHRoZXNlIGRhdGEgcG9pbnRzLiBUaGUgcGxvdF9seSBzeW50YXggaXMgc2ltaWxhciB0byB0aGF0IG9mIGdncGxvdC4gSW4gdGhpcyBjYXNlLCB0aGUgYWRkX21hcmtlcnMoKSBmdW5jdGlvbiBkcmF3cyB0aGUgcG9pbnRzLCB3aGlsZSB0aGUgYWRkX3N1cmZhY2UoKSBmdW5jdGlvbiBkcmF3cyB0aGUgcGxhbmUuIFRoZSB4LCB5LCBhbmQgcGxhbmUgb2JqZWN0cyB3ZXJlIGNyZWF0ZWQgaW4gY29kZSB0aGF0IHdlIGFyZW4ndCBzaG93aW5nIGhlcmUgZHVlIHRvIGl0cyBjb21wbGV4aXR5LgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KeCA8LSBzZXFfcmFuZ2UoYmFiaWVzJGdlc3RhdGlvbiwgbiA9IDYxNSkKeSA8LSBzZXFfcmFuZ2UoYmFiaWVzJGFnZSwgbiA9IDYxNSkKbmV3X2RhdGEgPC0gZGF0YS5mcmFtZShnZXN0YXRpb24gPSB4LCBhZ2UgPSB5KQpwbGFuZSA8LSBtYXRyaXgobnJvdyA9IDYxNSwgbmNvbCA9IDYxNSkKZm9yIChpIGluIHNlcV9hbG9uZyh4KSkgewogIHBsYW5lWywgaV0gPC0gcHJlZGljdChtb2QxLCBkYXRhLmZyYW1lKGdlc3RhdGlvbiA9IHgsIGFnZSA9IHlbaV0pKQp9ICAKYGBgCgpgYGB7cn0KcGxvdF9seShkYXRhID0gYmFiaWVzLCB6ID0gfmJ3dCwgeCA9IH5nZXN0YXRpb24sIHkgPSB+YWdlLCBvcGFjaXR5ID0gMC42KSAlPiUgCiAgYWRkX21hcmtlcnMobWFya2VyID0gbGlzdChzaXplID0gMikpICU+JSAKICBhZGRfc3VyZmFjZSh4ID0gfngsIHkgPSB+eSwgeiA9IH5wbGFuZSwgc2hvd3NjYWxlID0gRkFMU0UsIGNtaW4gPSAwLCBjbWF4ID0gMSwgc3VyZmFjZWNvbG9yID0gJ2dyZXknLCBjb2xvcnNjYWxlID0gJ2dyZXknKQpgYGAKCioqOC4gTGV0J3MgcHJhY3RpY2UhKioKCk5vdyBpdCdzIHRpbWUgZm9yIHlvdSB0byBidWlsZCBzb21lIG11bHRpcGxlIHJlZ3Jlc3Npb24gbW9kZWxzLgoKIyMjICoqRml0dGluZyBhIE1MUiBtb2RlbCoqCgpJbiB0ZXJtcyBvZiB0aGUgUiBjb2RlLCBmaXR0aW5nIGEgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgaXMgZWFzeTogc2ltcGx5IGFkZCB2YXJpYWJsZXMgdG8gdGhlIG1vZGVsIGZvcm11bGEgeW91IHNwZWNpZnkgaW4gdGhlIGBsbSgpYCBjb21tYW5kLgoKSW4gYSBwYXJhbGxlbCBzbG9wZXMgbW9kZWwsIHdlIGhhZCB0d28gZXhwbGFuYXRvcnkgdmFyaWFibGVzOiBvbmUgd2FzIG51bWVyaWMgYW5kIG9uZSB3YXMgY2F0ZWdvcmljYWwuIEhlcmUsIHdlIHdpbGwgYWxsb3cgYm90aCBleHBsYW5hdG9yeSB2YXJpYWJsZXMgdG8gYmUgbnVtZXJpYy4KCioqSW5zdHJ1Y3Rpb25zKioKClRoZSBkYXRhc2V0IGBtYXJpb19rYXJ0YCBpcyBhbHJlYWR5IGxvYWRlZCBpbiB5b3VyIHdvcmtzcGFjZS4KCi0gRml0IGEgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgZm9yIHRvdGFsIHByaWNlIGFzIGEgZnVuY3Rpb24gb2YgdGhlIGR1cmF0aW9uIG9mIHRoZSBhdWN0aW9uIGFuZCB0aGUgc3RhcnRpbmcgcHJpY2UuCgpgYGB7cn0KIyBGaXQgdGhlIG1vZGVsIHVzaW5nIGR1cmF0aW9uIGFuZCBzdGFydFByCmxtKHRvdGFsUHIgfiBkdXJhdGlvbiArIHN0YXJ0UHIsIGRhdGEgPSBtYXJpb19rYXJ0KQpgYGAKCiMjIyAqKlRpbGluZyB0aGUgcGxhbmUqKgoKT25lIG1ldGhvZCBmb3IgdmlzdWFsaXppbmcgYSBtdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBpcyB0byBjcmVhdGUgYSBoZWF0bWFwIG9mIHRoZSBmaXR0ZWQgdmFsdWVzIGluIHRoZSBwbGFuZSBkZWZpbmVkIGJ5IHRoZSB0d28gZXhwbGFuYXRvcnkgdmFyaWFibGVzLiBUaGlzIGhlYXRtYXAgd2lsbCBpbGx1c3RyYXRlIGhvdyB0aGUgbW9kZWwgb3V0cHV0IGNoYW5nZXMgb3ZlciBkaWZmZXJlbnQgY29tYmluYXRpb25zIG9mIHRoZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMuCgpUaGlzIGlzIGEgbXVsdGlzdGVwIHByb2Nlc3M6CgogIC0gRmlyc3QsIGNyZWF0ZSBhIGdyaWQgb2YgdGhlIHBvc3NpYmxlIHBhaXJzIG9mIHZhbHVlcyBvZiB0aGUgZXhwbGFuYXRvcnkgdmFyaWFibGVzLiBUaGUgZ3JpZCBzaG91bGQgYmUgb3ZlciB0aGUgYWN0dWFsIHJhbmdlIG9mIHRoZSBkYXRhIHByZXNlbnQgaW4gZWFjaCB2YXJpYWJsZS4gV2UndmUgZG9uZSB0aGlzIGZvciB5b3UgYW5kIHN0b3JlZCB0aGUgcmVzdWx0IGFzIGEgZGF0YSBmcmFtZSBjYWxsZWQgYGdyaWRgLgogIC0gVXNlIGBhdWdtZW50KClgIHdpdGggdGhlIGBuZXdkYXRhYCBhcmd1bWVudCB0byBmaW5kIHRoZSAkXGhcdHt5JH0ncyBjb3JyZXNwb25kaW5nIHRvIHRoZSB2YWx1ZXMgaW4gYGdyaWRgLgogIC0gQWRkIHRoZXNlIHRvIHRoZSBgZGF0YV9zcGFjZWAgcGxvdCBieSB1c2luZyB0aGUgYGZpbGxgIGFlc3RoZXRpYyBhbmQgYGdlb21fdGlsZSgpYC4KCmBgYHtyLCBlY2hvID0gRkFMU0V9CmdyaWQgPC0gbWFyaW9fa2FydCAlPiUgCiAgZGF0YV9ncmlkKAogICAgZHVyYXRpb24gPSBzZXFfcmFuZ2UoZHVyYXRpb24sIGJ5ID0gMSksCiAgICBzdGFydFByID0gc2VxX3JhbmdlKHN0YXJ0UHIsIGJ5ID0gMSkKICApCgpkYXRhX3NwYWNlIDwtIGdncGxvdChkYXRhID0gbWFyaW9fa2FydCwgYWVzKHggPSBkdXJhdGlvbiwgeSA9IHN0YXJ0UHIpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSB0b3RhbFByKSkKCm1vZCA8LSBsbSh0b3RhbFByIH4gZHVyYXRpb24gKyBzdGFydFByLCBkYXRhID0gbWFyaW9fa2FydCkKYGBgCgoqKkluc3RydWN0aW9ucyoqCgpUaGUgbW9kZWwgb2JqZWN0IGBtb2RgIGlzIGFscmVhZHkgaW4geW91ciB3b3Jrc3BhY2UuCgotIFVzZSBgYXVnbWVudCgpYCB0byBjcmVhdGUgYSBgZGF0YS5mcmFtZWAgdGhhdCBjb250YWlucyB0aGUgdmFsdWVzIHRoZSBtb2RlbCBvdXRwdXRzIGZvciBlYWNoIHJvdyBvZiBgZ3JpZGAuCi0gVXNlIGBnZW9tX3RpbGVgIHRvIGlsbHVzdHJhdGUgdGhlc2UgcHJlZGljdGVkIHZhbHVlcyBvdmVyIHRoZSBgZGF0YV9zcGFjZWAgcGxvdC4gVXNlIHRoZSBgZmlsbGAgYWVzdGhldGljIGFuZCBzZXQgYGFscGhhID0gMC41YC4KCmBgYHtyfQojIGFkZCBwcmVkaWN0aW9ucyB0byBncmlkCnByaWNlX2hhdHMgPC0gYXVnbWVudChtb2QsIG5ld2RhdGEgPSBncmlkKQoKIyB0aWxlIHRoZSBwbGFuZQpkYXRhX3NwYWNlICsgCiAgZ2VvbV90aWxlKGRhdGEgPSBwcmljZV9oYXRzLCBhZXMoZmlsbCA9IC5maXR0ZWQpLCBhbHBoYSA9IDAuNSkgCmBgYAoKIyMjICoqTW9kZWxzIGluIDNEKioKCkFuIGFsdGVybmF0aXZlIHdheSB0byB2aXN1YWxpemUgYSBtdWx0aXBsZSByZWdyZXNzaW9uIG1vZGVsIHdpdGggdHdvIG51bWVyaWMgZXhwbGFuYXRvcnkgdmFyaWFibGVzIGlzIGFzIGEgcGxhbmUgaW4gdGhyZWUgZGltZW5zaW9ucy4gVGhpcyBpcyBwb3NzaWJsZSBpbiBSIHVzaW5nIHRoZSBgcGxvdGx5YCBwYWNrYWdlLgoKV2UgaGF2ZSBjcmVhdGVkIHRocmVlIG9iamVjdHMgdGhhdCB5b3Ugd2lsbCBuZWVkOgoKICAtIGB4YDogYSB2ZWN0b3Igb2YgdW5pcXVlIHZhbHVlcyBvZiBgZHVyYXRpb25gCiAgLSBgeWA6IGEgdmVjdG9yIG9mIHVuaXF1ZSB2YWx1ZXMgb2YgYHN0YXJ0UHJgCiAgLSBgcGxhbmVgOiBhIG1hdHJpeCBvZiB0aGUgZml0dGVkIHZhbHVlcyBhY3Jvc3MgYWxsIGNvbWJpbmF0aW9ucyBvZiBgeGAgYW5kIGB5YAoKYGBge3J9CnggPC0gc2VxX3JhbmdlKG1hcmlvX2thcnQkZHVyYXRpb24sIG4gPSA3MCkKeSA8LSBzZXFfcmFuZ2UobWFyaW9fa2FydCRzdGFydFByLCBuID0gNzApCm5ld19kYXRhIDwtIGRhdGEuZnJhbWUoZHVyYXRpb24gPSB4LCBzdGFydFByID0geSkKcGxhbmUgPC0gbWF0cml4KG5yb3cgPSA3MCwgbmNvbCA9IDcwKQpmb3IgKGkgaW4gc2VxX2Fsb25nKHgpKSB7CiAgcGxhbmVbLCBpXSA8LSBwcmVkaWN0KG1vZCwgZGF0YS5mcmFtZShkdXJhdGlvbiA9IHgsIHN0YXJ0UHIgPSB5W2ldKSkKfSAgCgpgYGAKCk11Y2ggbGlrZSBgZ2dwbG90KClgLCB0aGUgYHBsb3RfbHkoKWAgZnVuY3Rpb24gd2lsbCBhbGxvdyB5b3UgdG8gY3JlYXRlIGEgcGxvdCBvYmplY3Qgd2l0aCB2YXJpYWJsZXMgbWFwcGVkIHRvIGB4YCwgYHlgLCBhbmQgYHpgIGFlc3RoZXRpY3MuIFRoZSBgYWRkX21hcmtlcnMoKWAgZnVuY3Rpb24gaXMgc2ltaWxhciB0byBgZ2VvbV9wb2ludCgpYCBpbiB0aGF0IGl0IGFsbG93cyB5b3UgdG8gYWRkIHBvaW50cyB0byB5b3VyIDNEIHBsb3QuCgpOb3RlIHRoYXQgYHBsb3RfbHlgIHVzZXMgdGhlIHBpcGUgKGAlPiVgKSBvcGVyYXRvciB0byBjaGFpbiBjb21tYW5kcyB0b2dldGhlci4KCioqSW5zdHJ1Y3Rpb25zKioKCi0gUnVuIHRoZSBgcGxvdF9seWAgY29tbWFuZCB0byBkcmF3IDNEIHNjYXR0ZXJwbG90IGZvciBgdG90YWxQcmAgYXMgYSBmdW5jdGlvbiBvZiBgZHVyYXRpb25gIGFuZCBgc3RhcnRQcmAgYnkgbWFwcGluZyB0aGUgYHpgIHZhcmlhYmxlIHRvIHRoZSByZXNwb25zZSBhbmQgdGhlIGB4YCBhbmQgYHlgIHZhcmlhYmxlcyB0byB0aGUgZXhwbGFuYXRvcnkgdmFyaWFibGVzLiBEdXJhdGlvbiBzaG91bGQgYmUgb24gdGhlIHgtYXhpcyBhbmQgc3RhcnRpbmcgcHJpY2Ugc2hvdWxkIGJlIG9uIHRoZSB5LWF4aXMuCi0gVXNlIGBhZGRfc3VyZmFjZSgpYCB0byBkcmF3IGEgcGxhbmUgdGhyb3VnaCB0aGUgY2xvdWQgb2YgcG9pbnRzIGJ5IHNldHRpbmcgYHogPSB+cGxhbmVgLgoKYGBge3J9CiMgZHJhdyB0aGUgM0Qgc2NhdHRlcnBsb3QKcCA8LSBwbG90X2x5KGRhdGEgPSBtYXJpb19rYXJ0LCB6ID0gfnRvdGFsUHIsIHggPSB+ZHVyYXRpb24sIHkgPSB+c3RhcnRQciwgb3BhY2l0eSA9IDAuNikgJT4lCiAgYWRkX21hcmtlcnMobWFya2VyID0gbGlzdChzaXplID0gMikpIAogIAojIGRyYXcgdGhlIHBsYW5lCnAgJT4lCiAgYWRkX3N1cmZhY2UoeCA9IH54LCB5ID0gfnksIHogPSB+cGxhbmUsIHNob3dzY2FsZSA9IEZBTFNFKQpgYGAKCiMjICoqQ29uZGl0aW9uYWwgaW50ZXJwcmV0YXRpb24gb2YgY29lZmZpY2llbnRzKioKCioqMS4gQ29uZGl0aW9uYWwgaW50ZXJwcmV0YXRpb24gb2YgY29lZmZpY2llbnRzKioKCldoYXQgZG8gdGhlIGNvZWZmaWNpZW50cyBpbiBhIG11bHRpcGxlIHJlZ3Jlc3Npb24gbW9kZWwgbWVhbj8KCioqMi4gVHdvIHNsb3BlIGNvZWZmaWNpZW50cyoqCgpDb25zaWRlciB0aGUgZml0dGVkIGNvZWZmaWNpZW50cyBmb3Igb3VyIG1vZGVsIGZvciB0aGUgYmlydGh3ZWlnaHQgb2YgYmFiaWVzIHNob3duIGFib3ZlLiBTaW5jZSBib3RoIG9mIG91ciBleHBsYW5hdG9yeSB2YXJpYWJsZXMgYXJlIG51bWVyaWMsIHRoZSBjb2VmZmljaWVudHMgb2YgYm90aCBnZXN0YXRpb24gYW5kIGFnZSByZXByZXNlbnQgc2xvcGVzLiBCdXQgc2xvcGVzIG9mIHdoYXQ/IEEgbGluZSBjZXJ0YWlubHkgY2FuJ3QgaGF2ZSB0d28gc2xvcGVzLCBidXQgYXMgd2Ugc2F3IHByZXZpb3VzbHksIG91ciBtb2RlbCBpcyBub3QgYSBsaW5lOiBpdCBpcyBhIHBsYW5lLiBBbmQgYSBwbGFuZSBjYW4gaGF2ZSB0d28gc2xvcGVzLiBOb3RlIHRoYXQgaW4gdGhpcyBjYXNlIGJvdGggc2xvcGVzIGFyZSBwb3NpdGl2ZSwgYnV0IGhhdmUgZGlmZmVyZW50IG1hZ25pdHVkZXMuCgpgYGB7cn0KbW9kMQpgYGAKCioqMy4gVGlsZWQgcGxhbmUqKgoKV2hlbiB3ZSBkZXBpY3RlZCBvdXIgbW9kZWwgYXMgYSB0aWxlZCBzdXJmYWNlIGluIHRoZSBwbGFuZSwgd2Ugbm90aWNlZCB0aGF0IHRoZSBjb2xvciBvZiB0aGUgdGlsZXMgZ290IGxpZ2h0ZXIgYW5kIGJsdWVyIGFzIHdlIG1vdmVkIHRvd2FyZHMgdGhlIHVwcGVyIHJpZ2h0IGhhbmQgY29ybmVyIG9mIHRoZSBwbG90LiBUaGlzIHJlZmxlY3RzIHRoZSBmYWN0IHRoYXQgb3VyIG1vZGVsIHByZWRpY3RzIGhpZ2hlciBiaXJ0aHdlaWdodHMgZm9yIGJhYmllcyB3aXRoIGxvbmdlciBnZXN0YXRpb25hbCBwZXJpb2RzIGJvcm4gdG8gb2xkZXIgbW90aGVycy4gQnV0IGF0IHdoYXQgcmF0ZSBkb2VzIHRoaXMgY29sb3IgY2hhbmdlPwoKYGBge3J9Cm1vZGVfc3BhY2UxCmBgYAoKKio0LiBUaWxlZCBwbGFuZSBwbHVzIGZpcnN0IHNsb3BlKioKCkZvciBhIDMwLXllYXItb2xkIG1vdGhlciwgdGhlIGluY3JlYXNlIGluIGV4cGVjdGVkIGJpcnRod2VpZ2h0IGlzIDAgcG9pbnQgNDcgb3VuY2VzIHBlciBkYXksIGFuZCB0aGlzIGNhbiBiZSB2aWV3ZWQgb24gdGhlIHBsb3QgYXMgdGhlIHJhdGUgb2YgY29sb3IgY2hhbmdlIGFzIHlvdSBtb3ZlIGhvcml6b250YWxseSB0aHJvdWdoIHRoZSBwbG90IGFsb25nIGEgc3RyYWlnaHQgbGluZS4gSXQgaXMgaW4gdGhpcyBzZW5zZSB0aGF0IHRoZSBjb2VmZmljaWVudCBvZiBnZXN0YXRpb24gaXMgYSBzbG9wZS4gSG93ZXZlciwgdGhpcyByYXRlIG9mIGNoYW5nZSBpcyBjb25zdGFudCBhY3Jvc3MgbW90aGVycyBvZiBhbGwgYWdlcy4gSXQgZG9lc24ndCBtYXR0ZXIgd2hldGhlciB5b3UgYXJlIDIwLCAzMCwgb3IgNDAuIFdoaWxlIHRoZSBleHBlY3RlZCBiaXJ0aHdlaWdodCBkb2VzIGRlcGVuZCBvbiB0aGUgbW90aGVyJ3MgYWdlLCB0aGUgcmF0ZSBvZiBjaGFuZ2UgaW4gYmlydGh3ZWlnaHQgd2l0aCByZXNwZWN0IHRvIGdlc3RhdGlvbmFsIGxlbmd0aCBkb2VzIG5vdCBkZXBlbmQgb24gdGhlIHRoZSBtb3RoZXIncyBhZ2UuIFRodXMsIHRoZSBjb2VmZmljaWVudCBvZiAwLjQ3IG91bmNlcyBwZXIgZGF5IG9uIGdlc3RhdGlvbiByZWZsZWN0cyB0aGUgc2xvcGUgb2YgdGhlIHBsYW5lIGZvciBhIGZpeGVkIHZhbHVlIG9mIGFnZS4gQW5kIHNpbmNlIHRoaXMgc2xvcGUgZG9lc24ndCBjaGFuZ2Ugd2l0aCByZXNwZWN0IHRvIGFnZSwgd2Ugb2Z0ZW4gc2F5IHRoYXQgaXQgcmVmbGVjdHMgdGhlIGVmZmVjdCBvZiBnZXN0YXRpb25hbCBsZW5ndGggdXBvbiBiaXJ0aHdlaWdodCwgd2hpbGUgaG9sZGluZyBhZ2UgY29uc3RhbnQuCgpgYGB7cn0KbW9kZV9zcGFjZTEgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAzMCwgY29sb3IgPSAncmVkJykKYGBgCgoqKjUuIFRpbGVkIHBsYW5lIHBsdXMgc2Vjb25kIHNsb3BlKioKClNpbWlsYXJseSwgYXQgdGhlIHR5cGljYWwgZ2VzdGF0aW9uYWwgbGVuZ3RoIG9mIDQwIHdlZWtzIChvciAyODAgZGF5cyksIHRoZSBleHBlY3RlZCBiaXJ0aHdlaWdodCBpbmNyZWFzZXMgYXQgYSByYXRlIG9mIDAgcG9pbnQgMTcgb3VuY2VzIHBlciB5ZWFyIG9mIHRoZSBtb3RoZXIncyBhZ2UuIFRoYXQgaXMsIG91ciBtb2RlbCBwcmVkaWN0cyB0aGF0IHRoZSBiaXJ0aHdlaWdodCBvZiBhIDM2LXllYXItb2xkIG1vdGhlciBpcyBhYm91dCBvbmUtc2l4dGggb2YgYW4gb3VuY2UgaGVhdmllciB0aGFuIHRoZSBiaXJ0aHdlaWdodCBvZiBhIDM1LXllYXItb2xkIG1vdGhlciwgd2hlbiBib3RoIG1vdGhlcnMgY2FycnkgZnVsbCB0ZXJtLiBCdXQgYWdhaW4sIHRoYXQgcmF0ZSBvZiBjaGFuZ2UgaXMgdGhlIHNhbWUgaXJyZXNwZWN0aXZlIG9mIHRoZSBnZXN0YXRpb25hbCBsZW5ndGgsIHNvIHdlIHdvdWxkIGV4cGVjdCB0aGUgc2FtZSBkaWZmZXJlbmNlIGZvciBiYWJpZXMgYm9ybiBhdCAzNSB3ZWVrcywgNDAgd2Vla3MsIGFuZCA0MiB3ZWVrcy4gU28gdGhlIGNvZWZmaWNpZW50IG9mIDAgcG9pbnQgMTcgb3VuY2VzIHBlciB5ZWFyIGlzIHRoZSBzbG9wZSBvZiB0aGUgcGxhbmUgZm9yIGEgZml4ZWQgbGVuZ3RoIG9mIGdlc3RhdGlvbi4gQWdhaW4sIHNpbmNlIHRoaXMgc2xvcGUgaXMgdGhlIHNhbWUgZm9yIGFsbCBnZXN0YXRpb25hbCBsZW5ndGhzLCB3ZSBjYW4gaW50ZXJwcmV0IHRoaXMgY29lZmZpY2llbnQgYXMgdGhlIGVmZmVjdCBvZiB0aGUgbW90aGVyJ3MgYWdlIG9uIGJpcnRod2VpZ2h0LCB3aGlsZSBob2xkaW5nIGdlc3RhdGlvbmFsIGxlbmd0aCBjb25zdGFudC4gSXQncyBhbHNvIGFwcGFyZW50IGZyb20gdGhlIHBsb3QgdGhhdCB0aGUgY29sb3JzIGNoYW5nZSBtb3JlIHJhcGlkbHkgYXMgeW91IG1vdmUgaG9yaXpvbnRhbGx5IGFzIG9wcG9zZWQgdG8gdmVydGljYWxseS4gVGhpcyByZWZsZWN0cyB0aGUgZmFjdCB0aGF0IHRoZSBjb2VmZmljaWVudCBvbiBnZXN0YXRpb24gaXMgYmlnZ2VyIHRoYW4gdGhlIGNvZWZmaWNpZW50IG9uIGFnZS4KCmBgYHtyfQptb2RlX3NwYWNlMSArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDI4MCwgY29sb3IgPSAncmVkJykKYGBgCioqNi4gQ29lZmZpY2llbnQgaW50ZXJwcmV0YXRpb24qKgoKTm93LCB5b3UgbWlnaHQgYmUgdGVtcHRlZCB0byB0aGluayB0aGF0IGJpZ2dlciBjb2VmZmljaWVudHMgYXJlIGFsd2F5cyBtb3JlIGltcG9ydGFudCwgYnV0IHRoaXMgaXMgbm90IHRydWUuIFRoZSB2YWx1ZSBvZiB0aGUgY29lZmZpY2llbnRzIGRlcGVuZCBvbiB0aGUgdW5pdHMgb2YgdGhlIGV4cGxhbmF0b3J5IHZhcmlhYmxlcy4gSW4gdGhpcyBjYXNlLCBvbmUgdmFyaWFibGUgaXMgaW4gdGhlIHVuaXRzIG9mIGRheXMgKG9mIGdlc3RhdGlvbmFsIGxlbmd0aCksIHdoaWxlIHRoZSBvdGhlciBpcyBpbiB0aGUgdW5pdHMgb2YgeWVhcnMgKG9mIG1vdGhlcidzIGFnZSkuIFRoZXkgYXJlIG5vdCBkaXJlY3RseSBjb21wYXJhYmxlLiBXaGVuIHlvdSBpbnRlcnByZXQgY29lZmZpY2llbnRzIGZyb20gYSBtdWx0aXBsZSByZWdyZXNzaW9uIG1vZGVsLCBiZSBzdXJlIHRvIGFsd2F5cyBpbmNsdWRlIGEgcGhyYXNlIHRvIHRoZSBlZmZlY3Qgb2YgImhvbGRpbmcgeCBjb25zdGFudC4iIEFub3RoZXIgZ29vZCBhbHRlcm5hdGl2ZSBpcyAiYWZ0ZXIgY29udHJvbGxpbmcgZm9yIHguIiBUaGlzIGluZm9ybWF0aW9uIGlzIGNydWNpYWwgdG8gaGF2aW5nIGEgdmFsaWQgdW5kZXJzdGFuZGluZyBvZiBhIHJlZ3Jlc3Npb24gbW9kZWwuCgoqKjcuIExldCdzIHByYWN0aWNlISoqCgpZb3UnbGwgZGVtb25zdGF0ZSB5b3VyIHVuZGVyc3RhbmRpbmcgb2YgdGhlc2UgY29uY2VwdHMgaW4gdGhlIG5leHQgZXhlcmNpc2VzLgoKIyMgKipBZGRpbmcgYSB0aGlyZCAoY2F0ZWdvcmljYWwpIHZhcmlhYmxlKioKCioqMS4gQWRkaW5nIGEgdGhpcmQgKGNhdGVnb3JpY2FsKSB2YXJpYWJsZSoqCgoqKjIuIEhvdyBjb3VsZCB3ZSBmb3JnZXQgYWJvdXQgc21va2luZz8qKgoKTm8gc3R1ZHkgb2YgYmlydGh3ZWlnaHQgaXMgY29tcGxldGUgd2l0aG91dCBzb21lIGRpc2N1c3Npb24gb2YgdGhlIGVmZmVjdCBvZiBzbW9raW5nLCB3aGljaCBpcyBrbm93biB0byBoYXZlIGFsbCBraW5kcyBvZiB1bmRlc2lyZWFibGUgaGVhbHRoIGNvbnNlcXVlbmNlcy4gTW90aGVycyBpbiB0aGUgYmFiaWVzIGRhdGEgc2V0IGhhdmUgdGhlaXIgc21va2luZyBzdGF0dXMgcmVjb3JkZWQgYXMgYSBiaW5hcnkgdmFyaWFibGU6IGVpdGhlciBzaGUgd2FzIG9yIHdhcyBub3QgYSBzbW9rZXIuIEFkZGluZyB0aGlzIHRoaXJkIGV4cGxhbmF0b3J5IHZhcmlhYmxlIHRvIG91ciBtb2RlbCBpcyBhZ2FpbiBlYXN5LiBTaW5jZSB0aGlzIHZhcmlhYmxlIGlzIGVuY29kZWQgYXMgMCBvciAxLCBubyB0cmFuc2Zvcm1hdGlvbiBpcyBuZWNlc3NhcnkgYW5kIHdlIGNhbiBzaW1wbHkgYWRkIGFub3RoZXIgdGVybSB0byB0aGUgbWF0aGVtYXRpY2FsIG1vZGVsIGFuZCB0aGUgZm9ybXVsYSBzeW50YXggdG8gc3BlY2lmeSBvdXIgbmV3IG1vZGVsLgoKKiozLiBHZW9tZXRyeSoqCgpCdXQgaGVyZSBhZ2FpbiB0aGUgZ2VvbWV0cmljIGNoYW5nZXMgYXJlIG5vdCBhcyBlYXN5IHRvIHNlZS4gSG93ZXZlciwgb3VyIGVtcGhhc2lzIG9uIHRoZSBnZW9tZXRyeSBvZiB0aGVzZSBtb2RlbHMgc2hvdWxkIGdpdmUgeW91IHNvbWUgaW50dWl0aW9uLiBSZWNhbGwgdGhhdCB0aGUgYWRkaXRpb24gb2YgYSBjYXRlZ29yaWNhbCBleHBsYW5hdG9yeSB2YXJpYWJsZSB0byBhIG51bWVyaWMgZXhwbGFuYXRvcnkgdmFyaWFibGUgY2hhbmdlZCBhIGxpbmUgaW50byBwYXJhbGxlbCBsaW5lcy4gTW9yZW92ZXIsIGEgbW9kZWwgd2l0aCB0d28gbnVtZXJpYyBleHBsYW5hdG9yeSB2YXJpYWJsZXMgd2FzIGEgcGxhbmUuIENhbiB5b3UgZ3Vlc3MgaG93IHRoZSBhZGRpdGlvbiBvZiBhIGNhdGVnb3JpY2FsIGV4cGxhbmF0b3J5IHZhcmlhYmxlIHdpbGwgY2hhbmdlIHRoZSBnZW9tZXRyeSBvZiBhIG1vZGVsIHdpdGggdHdvIG51bWVyaWMgZXhwbGFuYXRvcnkgdmFyaWFibGVzPyBJZiB5b3UgZ3Vlc3MgcGFyYWxsZWwgcGxhbmVzLCB5b3UncmUgcmlnaHQhCgoqKjQuIERyYXdpbmcgcGFyYWxsZWwgcGxhbmVzIGluIDNEKioKCk9uY2UgYWdhaW4sIHdlIGNhbiB1c2UgcGxvdGx5IHRvIGNyZWF0ZSBhIDNEIGltYWdlIG9mIG91ciBtb2RlbCBpbiB0aGUgZGF0YSBzcGFjZS4gSGVyZSB3ZSBoYXZlIHVzZWQgdGhlIGFkZF9zdXJmYWNlKCkgZnVuY3Rpb24gdHdpY2U6IG9uY2UgdG8gYWRkIGEgcGxhbmUgZm9yIG5vbi1zbW9rZXJzLCBhbmQgYWdhaW4gdG8gYWRkIGFub3RoZXIgcGxhbmUgZm9yIHNtb2tlcnMuIFRoZSBwbGFuZSBvbiAidG9wIiBpcyB0aGUgb25lIGZvciBub24tc21va2Vycy0tLWNhbiB5b3UgdGhpbmsgb2Ygd2h5IHRoaXMgbWlnaHQgYmUgY2FzZT8gV2UnbGwgcmV0dXJuIHRvIHRoaXMgcXVlc3Rpb24gaW4gYSBtaW51dGUuCgpgYGB7ciwgZWNobyA9IEZBTFNFfQptb2QyIDwtIGxtKGJ3dCB+IGdlc3RhdGlvbiArIGFnZSArIHNtb2tlLCBkYXRhID0gYmFiaWVzKQp4IDwtIHNlcV9yYW5nZShiYWJpZXMkZ2VzdGF0aW9uLCBuID0gNjE2KQp5IDwtIHNlcV9yYW5nZShiYWJpZXMkYWdlLCBuID0gNjE2KQp3MCA8LSByZXAoMCwgZWFjaCA9IDYxNikKdzEgPC0gcmVwKDEsIGVhY2ggPSA2MTYpCm5ld19kYXRhIDwtIGRhdGEuZnJhbWUoZ2VzdGF0aW9uID0geCwgYWdlID0geSkKcGxhbmUwIDwtIG1hdHJpeChucm93ID0gNjE2LCBuY29sID0gNjE2KQpwbGFuZTEgPC0gbWF0cml4KG5yb3cgPSA2MTYsIG5jb2wgPSA2MTYpCmRmMCA8LSBkYXRhLmZyYW1lKGdlc3RhdGlvbiA9IHgsIGFnZSA9IHlbaV0sIHNtb2tlID0gdzApCmRmMSA8LSBkYXRhLmZyYW1lKGdlc3RhdGlvbiA9IHgsIGFnZSA9IHlbaV0sIHNtb2tlID0gdzEpCmZvciAoaSBpbiBzZXFfYWxvbmcoeCkpIHsKICBwbGFuZTBbLCBpXSA8LSBwcmVkaWN0KG1vZDIsIGRmMCkKfSAgCmZvciAoaSBpbiBzZXFfYWxvbmcoeCkpIHsKICBwbGFuZTFbLCBpXSA8LSBwcmVkaWN0KG1vZDIsIGRmMSkKfSAgCmBgYAoKYGBge3J9CnBsb3RfbHkoZGF0YSA9IGJhYmllcywgeiA9IH5id3QsIHggPSB+Z2VzdGF0aW9uLCB5ID0gfmFnZSwgb3BhY2l0eSA9IDAuNikgJT4lIAogIGFkZF9tYXJrZXJzKGNvbG9yID0gfmZhY3RvcihzbW9rZSksIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDIpKSAlPiUgCiAgYWRkX3N1cmZhY2UoeCA9IH54LCB5ID0gfnksIHogPSB+cGxhbmUwLCBzaG93c2NhbGUgPSBGQUxTRSwgY21pbiA9IDAsIGNtYXggPSAxLCBzdXJmYWNlY29sb3IgPSAncmVkJywgY29sb3JzY2FsZSA9ICdyZWQnKSAlPiUgCiAgYWRkX3N1cmZhY2UoeCA9IH54LCB5ID0gfnksIHogPSB+cGxhbmUxLCBzaG93c2NhbGUgPSBGQUxTRSwgY21pbiA9IDAsIGNtYXggPSAxLCBzdXJmYWNlY29sb3IgPSAnYmx1ZScsIGNvbG9yc2NhbGUgPSAnYmx1ZScpCmBgYAoKKio1LiBDb2VmZmljaWVudCBpbnRlcnByZXRhdGlvbioqCgpUaGUgaW50ZXJwcmV0YXRpb25zIHRoYXQgd2UgZGV2ZWxvcGVkIHByZXZpb3VzbHkgc3RpbGwgaG9sZC0tLXdlIGp1c3QgbmVlZCB0byB0aGluayBjYXJlZnVsbHkgYWJvdXQgaG93IHRoZXkgYXBwbHkgdG8gb3VyIG5ldyBtb2RlbC4gVGhlIGNvZWZmaWNpZW50cyBvbiBnZXN0YXRpb24gYW5kIGFnZSBzdGlsbCByZWZsZWN0IHNsb3Blcy4gQW5kIHNpbmNlIHRoZSBwbGFuZXMgYXJlIHBhcmFsbGVsLCBib3RoIHNsb3BlcyBhcmUgdGhlIHNhbWUgaW4gYm90aCBwbGFuZXMuIFRoZSBjb2VmZmljaWVudCBvbiBzbW9rZSBpcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgdHdvIHBsYW5lcywgYW5kIHRoaXMgZGlzdGFuY2UgaXMgY29uc3RhbnQgYWNyb3NzIGFsbCBwb3NzaWJsZSB2YWx1ZXMgb2YgZ2VzdGF0aW9uIGFuZCBhZ2UuIEluIHRoaXMgc2Vuc2UsIHdlIGFyZSBtb2RlbGluZyB0aGUgZWZmZWN0IG9mIHNtb2tpbmcgYXMgYmVpbmcgdGhlIHNhbWUgcmVnYXJkbGVzcyBvZiBnZXN0YXRpb25hbCBsZW5ndGggYW5kIHRoZSBtb3RoZXIncyBhZ2UuIEhvd2V2ZXIsIHdlIGhhdmUgZXN0aW1hdGVkIHRoZSBzaXplIG9mIHRoYXQgZWZmZWN0IGluIHRoZSBjb250ZXh0IG9mIGdlc3RhdGlvbmFsIGxlbmd0aCBhbmQgdGhlIG1vdGhlcidzIGFnZS4gVGhlIGNvZWZmaWNpZW50IG9mIGdlc3RhdGlvbiBpcyAwLjQ1IG91bmNlcyBwZXIgZGF5LCB3aGljaCBpcyBvbmx5IHNsaWdodGx5IGxlc3MgdGhhbiBpdCB3YXMgaW4gb3VyIHByZXZpb3VzIG1vZGVsIHRoYXQgZGlkIG5vdCBjb25zaWRlciBzbW9raW5nLiBPdXIgaW50ZXJwcmV0YXRpb24gaXMgdGhhdCBlYWNoIGFkZGl0aW9uYWwgZGF5IG9mIGdlc3RhdGlvbiB3YXMgYXNzb2NpYXRlZCB3aXRoIGFuIGluY3JlYXNlIGluIGV4cGVjdGVkIGJpcnRod2VpZ2h0IG9mIDAuNDUgb3VuY2VzLCBhZnRlciBjb250cm9sbGluZyBmb3IgdGhlIG1vdGhlcidzIGFnZSBhbmQgc21va2luZyBzdGF0dXMuIEVhY2ggYWRkaXRpb25hbCB5ZWFyIG9mIHRoZSBtb3RoZXIncyBhZ2Ugd2FzIGFzc29jaWF0ZWQgd2l0aCBhbiBpbmNyZWFzZSBpbiBleHBlY3RlZCBiaXJ0aHdlaWdodCBvZiBqdXN0IDAuMTEgb3VuY2VzIHBlciB5ZWFyLCBhZnRlciBjb250cm9sbGluZyBmb3IgZ2VzdGF0aW9uYWwgbGVuZ3RoIGFuZCB0aGUgbW90aGVyJ3Mgc21va2luZyBzdGF0dXMuIE5vdGUgdGhhdCB0aGUgZWZmZWN0IG9mIHRoZSBtb3RoZXIncyBhZ2Ugb24gYmlydGh3ZWlnaHQgYXBwZWFycyBsb3dlciB0aGFuIGluIHRoZSBwcmV2aW91cyBtb2RlbC4gTGV0J3MgZm9jdXMgbm93IG9uIHRoZSBlZmZlY3Qgb2Ygc21va2luZy4gVGhlIGNvZWZmaWNpZW50IG9uIHNtb2tlIGlzIC04LjAxIG91bmNlcy4gVGh1cywgb3VyIG1vZGVsIHByZWRpY3RzIHRoYXQgbW90aGVycyB3aG8gc21va2Ugd2lsbCBkZWxpdmVyIGJhYmllcyB0aGF0IHdlaWdodCA4IG91bmNlcyBsZXNzLCBvbiBhdmVyYWdlLCB0aGFuIG1vdGhlcnMgb2YgdGhlIHNhbWUgYWdlIGFuZCBnZXN0YXRpb25hbCBsZW5ndGggd2hvIGRvbid0IHNtb2tlLiBUaGF0IGlzLCB0aGUgbmVnYXRpdmUgZWZmZWN0IG9mIHNtb2tpbmcgb24gZXhwZWN0ZWQgYmlydGh3ZWlnaHQgaXMgOCBvdW5jZXMgKG9yIGhhbGYgYSBwb3VuZCksIGFmdGVyIGNvbnRyb2xsaW5nIGZvciBnZXN0YXRpb25hbCBsZW5ndGggYW5kIHRoZSBtb3RoZXIncyBhZ2UuIFRoaXMgaXMgYSAqaHVnZSogZWZmZWN0IGNvbXBhcmVkIHRvIHRoZSBpbmZsdWVuY2Ugb2YgdGhlIG90aGVyIHR3byB2YXJpYWJsZXMgd2UgaGF2ZSBjb25zaWRlcmVkLgoKYGBge3J9Cm1vZDE7IG1vZDIKYGBgCgoqKjYuIExldCdzIHByYWN0aWNlISoqCgpOb3cgaXQncyB5b3VyIHR1cm4gdG8gYnVpbGQgYW5kIGludGVycHJldCBzb21lIHBhcmFsbGVsIHBsYW5lcyBtb2RlbHMuCgojIyMgKipWaXN1YWxpemluZyBwYXJhbGxlbCBwbGFuZXMqKgoKQnkgaW5jbHVkaW5nIHRoZSBkdXJhdGlvbiwgc3RhcnRpbmcgcHJpY2UsIGFuZCBjb25kaXRpb24gdmFyaWFibGVzIGluIG91ciBtb2RlbCwgd2Ugbm93IGhhdmUgdHdvIGV4cGxhbmF0b3J5IHZhcmlhYmxlcyBhbmQgb25lIGNhdGVnb3JpY2FsIHZhcmlhYmxlLiBPdXIgbW9kZWwgbm93IHRha2VzIHRoZSBnZW9tZXRyaWMgZm9ybSBvZiB0d28gcGFyYWxsZWwgcGxhbmVzIQoKVGhlIGZpcnN0IHBsYW5lIGNvcnJlc3BvbmRzIHRvIHRoZSBtb2RlbCBvdXRwdXQgd2hlbiB0aGUgY29uZGl0aW9uIG9mIHRoZSBpdGVtIGlzIGBuZXdgLCB3aGlsZSB0aGUgc2Vjb25kIHBsYW5lIGNvcnJlc3BvbmRzIHRvIHRoZSBtb2RlbCBvdXRwdXQgd2hlbiB0aGUgY29uZGl0aW9uIG9mIHRoZSBpdGVtIGlzIGB1c2VkYC4gVGhlIHBsYW5lcyBoYXZlIHRoZSBzYW1lIHNsb3BlcyBhbG9uZyBib3RoIHRoZSBkdXJhdGlvbiBhbmQgc3RhcnRpbmcgcHJpY2UgYXhlc+KAlGl0IGlzIHRoZSAkeiQtaW50ZXJjZXB0IHRoYXQgaXMgZGlmZmVyZW50LgoKT25jZSBhZ2FpbiB3ZSBoYXZlIHN0b3JlZCB0aGUgYHhgIGFuZCBgeWAgdmVjdG9ycyBmb3IgeW91LiBTaW5jZSB3ZSBub3cgaGF2ZSB0d28gcGxhbmVzLCB0aGVyZSBhcmUgbWF0cml4IG9iamVjdHMgYHBsYW5lMGAgYW5kIGBwbGFuZTFgIHN0b3JlZCBmb3IgeW91IGFzIHdlbGwuCgpgYGB7cn0KbW9kIDwtIGxtKHRvdGFsUHIgfiBkdXJhdGlvbiArIHN0YXJ0UHIgKyBmYWN0b3IoY29uZCksIGRhdGEgPSBtYXJpb19rYXJ0KQp4IDwtIHNlcV9yYW5nZShtYXJpb19rYXJ0JGR1cmF0aW9uLCBuID0gNzApCnkgPC0gc2VxX3JhbmdlKG1hcmlvX2thcnQkc3RhcnRQciwgbiA9IDcwKQp3MCA8LSByZXAoJ25ldycsIDcwKQp3MSA8LSByZXAoJ3VzZWQnLCA3MCkKbmV3X2RhdGEwIDwtIGRhdGEuZnJhbWUoZHVyYXRpb24gPSB4LCBzdGFydFByID0geSwgY29uZCA9IHcwKQpuZXdfZGF0YTEgPC0gZGF0YS5mcmFtZShkdXJhdGlvbiA9IHgsIHN0YXJ0UHIgPSB5LCBjb25kID0gdzEpCnBsYW5lMCA8LSBtYXRyaXgobnJvdyA9IDcwLCBuY29sID0gNzApCmZvciAoaSBpbiBzZXFfYWxvbmcoeCkpIHsKICBwbGFuZTBbLCBpXSA8LSBwcmVkaWN0KG1vZCwgZGF0YS5mcmFtZShkdXJhdGlvbiA9IHgsIHN0YXJ0UHIgPSB5W2ldLCBjb25kID0gZmFjdG9yKHcwKSkpCn0gIApwbGFuZTEgPC0gbWF0cml4KG5yb3cgPSA3MCwgbmNvbCA9IDcwKQpmb3IgKGkgaW4gc2VxX2Fsb25nKHgpKSB7CiAgcGxhbmUxWywgaV0gPC0gcHJlZGljdChtb2QsIGRhdGEuZnJhbWUoZHVyYXRpb24gPSB4LCBzdGFydFByID0geVtpXSwgY29uZCA9IHcxKSkKfQpgYGAKCioqSW5zdHJ1Y3Rpb25zKioKCi0gVXNlIGBwbG90X2x5YCB0byBkcmF3IDNEIHNjYXR0ZXJwbG90IGZvciBgdG90YWxQcmAgYXMgYSBmdW5jdGlvbiBvZiBgZHVyYXRpb25gLCBgc3RhcnRQcmAsIGFuZCBgY29uZGAgYnkgbWFwcGluZyB0aGUgYHpgIHZhcmlhYmxlIHRvIHRoZSByZXNwb25zZSBhbmQgdGhlIGB4YCBhbmQgYHlgIHZhcmlhYmxlcyB0byB0aGUgZXhwbGFuYXRvcnkgdmFyaWFibGVzLiBEdXJhdGlvbiBzaG91bGQgYmUgb24gdGhlIHgtYXhpcyBhbmQgc3RhcnRpbmcgcHJpY2Ugc2hvdWxkIGJlIG9uIHRoZSB5LWF4aXMuIFVzZSBjb2xvciB0byByZXByZXNlbnQgYGNvbmRgLgotIFVzZSBgYWRkX3N1cmZhY2UoKWAgKHR3aWNlKSB0byBkcmF3IHR3byBwbGFuZXMgdGhyb3VnaCB0aGUgY2xvdWQgb2YgcG9pbnRzLCBvbmUgZm9yIG5ldyBNYXJpb0thcnRzIGFuZCBhbm90aGVyIGZvciB1c2VkIG9uZXMuIFVzZSB0aGUgb2JqZWN0cyBgcGxhbmUwYCBhbmQgYHBsYW5lMWAuCgpgYGB7cn0KIyBkcmF3IHRoZSAzRCBzY2F0dGVycGxvdApwIDwtIHBsb3RfbHkoZGF0YSA9IG1hcmlvX2thcnQsIHogPSB+dG90YWxQciwgeCA9IH5kdXJhdGlvbiwgeSA9IH5zdGFydFByLCBvcGFjaXR5ID0gMC42KSAlPiUKICBhZGRfbWFya2Vycyhjb2xvciA9IH5jb25kKSAKICAKIyBkcmF3IHR3byBwbGFuZXMKcCAlPiUKICBhZGRfc3VyZmFjZSh4ID0gfngsIHkgPSB+eSwgeiA9IH5wbGFuZTAsIHNob3dzY2FsZSA9IEZBTFNFKSAlPiUKICBhZGRfc3VyZmFjZSh4ID0gfngsIHkgPSB+eSwgeiA9IH5wbGFuZTEsIHNob3dzY2FsZSA9IEZBTFNFKQpgYGAKCiMjICoqSGlnaGVyIGRpbWVuc2lvbnMqKgoKKioxLiBIaWdoZXIgZGltZW5zaW9ucyoqCgoqKjIuIEFkZGluZyBtb3JlIHZhcmlhYmxlcyoqCgpCeSBub3csIHlvdSBtYXkgaGF2ZSBjYXVnaHQgb24gdG8gdGhlIGZhY3QgdGhhdCB0aGVyZSBhcmUgbm8gbWF0aGVtYXRpY2FsIG9yIHN5bnRhY3RpY2FsIGh1cmRsZXMgdG8gYWRkaW5nIG1vcmUgdmFyaWFibGVzIHRvIGEgbXVsdGlwbGUgcmVncmVzc2lvbiBtb2RlbC4gWW91IGNhbiBhZGQgYXMgbWFueSB0ZXJtcyBhcyB5b3Ugd2FudCBhbmQgUiB3aWxsIGhhcHBpbHkgZml0IHRoZSBtb2RlbCBmb3IgeW91LiBJbiB0aGlzIGNhc2Ugd2UgYWRkZWQgZXhwbGFuYXRvcnkgdmFyaWFibGVzIGZvciB0aGUgbW90aGVyJ3MgaGVpZ2h0IGFuZCB3ZWlnaHQsIGFuZCBhIHZhcmlhYmxlIGNhbGxlZCBwYXJpdHkgdGhhdCBkZW5vdGVzIHdoZXRoZXIgdGhpcyBjaGlsZCB3YXMgaGVyIGZpcnN0IHByZWduYW5jeS4gSXQgc2VlbXMgcmVhc29uYWJsZSB0byB0aGluayB0aGF0IGFueSBvZiB0aGVzZSB2YXJpYWJsZXMgbWlnaHQgcGxheSBhIHJvbGUgaW4gZGV0ZXJtaW5pbmcgYSBjaGlsZCdzIGJpcnRod2VpZ2h0LCBhbmQgc28gdGhlcmUgbWF5IGJlIHZhbGlkIHNjaWVudGlmaWMgcmVhc29ucyBmb3IgaW5jbHVkaW5nIGFueSBvciBhbGwgb2YgdGhlc2UgdmFyaWFibGVzIGluIG91ciBtb2RlbC4gSW4gUiwgeW91IGNhbiB1c2UgdGhlIGRvdCBvcGVyYXRvciBpbiBhIGZvcm11bGEgdG8gbWVhbiAiYWxsIG90aGVyIHZhcmlhYmxlcyBpbiB0aGUgZGF0YS5mcmFtZS4iIEluIHRoZSBsYXN0IGV4cHJlc3Npb24gYWJvdmUsIHdlIGhhdmUgYnVpbHQgdGhlICJraXRjaGVuIHNpbmsiIG1vZGVsIGJ5IHRocm93aW5nIGV2ZXJ5dGhpbmcgaW4gd2l0aCB0aGUgZG90IG9wZXJhdG9yLS0td2l0aCBvbmUgZXhjZXB0aW9uLiBUaGVyZSBhcmUgb2Z0ZW4gdmFyaWFibGVzIHRoYXQgYXJlIHJlY29yZGVkIHRoYXQgd2lsbCBub3QgbWFrZSBzZW5zZSB0byBpbmNsdWRlIGluIGEgbW9kZWwuIFRoZSBjYXNlIHZhcmlhYmxlIGluIHRoZSBiYWJpZXMgZGF0YSBzZXQgc2ltcGx5IGlkZW50aWZpZXMgZWFjaCBjYXNlLS0taXQgaXMgbm90IGFuIG9ic2VydmVkIHByb3BlcnR5IG9mIGVhY2ggYmlydGguIFRoZXJlZm9yZSwgd2UgZXhjbHVkZSBpdCBleHBsaWNpdHkuIFlvdSBjYW4gcmVhZCB0aGUgZG90IC0gY2FzZSBmb3JtdWxhIGFzICJhbGwgdmFyaWFibGVzIGV4Y2VwdCBmb3IgdGhlIG9uZSBuYW1lZCBjYXNlLiIKCmBgYHtyfQpsbShid3QgfiBnZXN0YXRpb24gKyBhZ2UgKyBzbW9rZSArIGhlaWdodCArIHdlaWdodCArIHBhcml0eSwgZGF0YSA9IGJhYmllcykKCiMgc2FtZSBtb2RlbCAoYnV0IG5vdGUgb3JkZXIgb2YgY29lZmZpY2llbnRzKQpsbShid3Qgfi4gLSBjYXNlLCBkYXRhID0gYmFiaWVzKQpgYGAKCioqMy4gSGlnaGVyIGRpbWVuc2lvbmFsIGdlb21ldHJ5KioKCllvdSBtYXkgYWxzbyBoYXZlIGNhdWdodCBvbiB0byB0aGUgZmFjdCB0aGF0IGEgZ2VvbWV0cmljIGludGVycHJldGF0aW9uIG9mIHRoZXNlIGhpZ2hlciBvcmRlciBtb2RlbHMgaXMgZXh0cmVtZWx5IGRpZmZpY3VsdC4gVGhlcmUgYXJlIHRoaW5ncyBjYWxsZWQgaHlwZXJwbGFuZXMgd2hpY2ggZ2VuZXJhbGl6ZSB0aGUgbm90aW9uIG9mIGEgcGxhbmUgdG8gaGlnaGVyIGRpbWVuc2lvbnMsIGJ1dCBzaW5jZSB3ZSBodW1hbnMgY2FuJ3QgdmlzdWFsaXplIG1vcmUgdGhhbiB0aHJlZSBzcGF0aWFsIGRpbWVuc2lvbnMgYW55d2F5LCB0aGVyZSBpc24ndCBtdWNoIGhvcGUgb2YgdmlzdWFsaXppbmcgb3VyIGhpZ2hlciBkaW1lbnNpb25hbCBtb2RlbHMgaW4gdGhlaXIgZnVsbCBnbG9yeS4gV2UgY2FuIG9mIGNvdXJzZSBlbXBsb3kgdHJpY2tzIGxpa2UgbWFwcGluZyB2YXJpYWJsZXMgdG8gY29sb3IsIGNyZWF0aW5nIHNtYWxsIG11bHRpcGxlcyBmb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCBhbmQgcHJvamVjdGluZyBpbnRvIGEgbG93ZXItZGltZW5zaW9uYWwgc3BhY2UgYnkgaGFyZC1jb2RpbmcgdGhlIHZhbHVlcyBvZiBzb21lIHZhcmlhYmxlcy4gV2Ugd29uJ3QgZXhwbG9yZSB0aGF0IGZ1cnRoZXIgaGVyZSBzaW5jZSB0aGVzZSBwbG90cyBhcmUgdW5saWtlbHkgdG8gcmVpbmZvcmNlIHlvdXIgZ2VvbWV0cmljIGludHVpdGlvbi4KCioqNC4gSW50ZXJwcmV0YXRpb24gaW4gbGFyZ2UgbW9kZWxzKioKClRoZSBpbnRlcnByZXRhdGlvbiBvZiB0aGUgY29lZmZpY2llbnRzIGluIGxhcmdlciBtb2RlbHMgcmVtYWlucyB0aGUgc2FtZS4gV2UgY2FuIHN0aWxsIHRoaW5rIG9mIHRoZSBjb2VmZmljaWVudHMgb24gbnVtZXJpYyBleHBsYW5hdG9yeSB2YXJpYWJsZXMgYXMgYmVpbmcgc2xvcGVzLCBhbmQgd2UgY2FuIHN0aWxsIHRoaW5rIG9mIHRoZSBjb2VmZmljaWVudHMgb24gY2F0ZWdvcmljYWwgZXhwbGFuYXRvcnkgdmFyaWFibGVzIGFzIGJlaW5nIGludGVyY2VwdHMuIFRoZSBtYWluIHRoaW5nIHdlIGhhdmUgdG8gYmUgY2FyZWZ1bCBhYm91dCBpcyByZW1lbWJlcmluZyB0byBpbmNsdWRlIGxhbmd1YWdlIHNwZWNpZnlpbmcgdGhlIG90aGVyIHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwuIEhlcmUsIG91ciBtYWluIGZpbmRpbmcgd291bGQgbGlrZWx5IGJlIHRoYXQgdGhlIGV4cGVjdGVkIGJpcnRod2VpZ2h0IG9mIGJhYmllcyBib3JuIHRvIG1vdGhlcnMgd2hvIHNtb2tlIGlzIDguNCBvdW5jZXMgbG93ZXIgdGhhbiBtb3RoZXJzIHdobyBkb24ndCwgYWZ0ZXIgY29udHJvbGxpbmcgZm9yIGdlc3RhdGlvbmFsIGxlbmd0aCwgYW5kIGhlciBhZ2UsIGhlaWdodCwgd2VpZ2h0LCBhbmQgd2hldGhlciBzaGUgaGFkIHByZXZpb3VzIHByZWduYW5jaWVzLgoKYGBge3J9CmxtKGJ3dCB+IGdlc3RhdGlvbiArIGFnZSArIHNtb2tlICsgaGVpZ2h0ICsgd2VpZ2h0ICsgcGFyaXR5LCBkYXRhID0gYmFiaWVzKQpgYGAKCioqNS4gTGV0J3MgcHJhY3RpY2UhKioKCkxldCdzIHNlZSBpZiB5b3UgY2FuIGZpbmQgYSBjb3JyZWN0IGludGVycHJldGF0aW9uIGZvciBjb2VmZmljaWVudHMgaW4gYSBsYXJnZSBtb2RlbC4KCg==